﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections;
using Noris.Tools.TraceVisualiser.Support;

namespace Djs.Tools.XmlPersistor
{
	#region class TypeLibrary : knihovna informací o datových typech a o postupech jejich konverze. Instancuje se 1x pro save/load, obsahuje položky XmlPersistTypeInfo pro každý nalezený Type.
	/// <summary>
	/// TypeLibrary : knihovna informací o datových typech a o postupech jejich konverze.
	/// Instancuje se 1x pro save/load, obsahuje položky TypeInfo pro každý nalezený Type.
	/// Každý nalezený TypeInfo popisuje datový typ a způsob jeho persistence.
	/// Pokud je typ složený z dalších property, pak TypeInfo obsahuje seznam PropInfo.
	/// </summary>
	internal class TypeLibrary : IDisposable
	{
		#region Konstrukce
		internal TypeLibrary()
		{
			this.TypeInfos = new Dictionary<Type, TypeInfo>();
			this._PrepareTypes();
		}
		private Dictionary<Type, TypeInfo> TypeInfos;
		void IDisposable.Dispose()
		{
			this.TypeInfos.Clear();
			this.TypeInfos = null;

			this._DisposeTypes();
		}
		#endregion
		#region Hledání / vytvoření informací o typu
		/// <summary>
		/// Vrátí informaci o daném typu včetně informací sloužících k řízení jeho persistence.
		/// </summary>
		/// <param name="type"></param>
		/// <returns></returns>
		internal TypeInfo GetInfo(Type type)
		{
			TypeInfo info;
			if (this.TypeInfos.TryGetValue(type, out info))
				return info;

			// TypeInfo musím vytvořit, NEmapovat! a ihned vložit do this (Library):
			info = new TypeInfo(this, type);
			this.TypeInfos.Add(type, info);

			// Teprve po vložení do Library mohu typ zmapovat:
			// Proč? Protože existují rekurzivní typy (i vzdáleně rekurzivní), 
			//   a ty by se v opačném případě (nejprve komplet zmapování typu, a pak teprve jeho vložení do Library) zamotaly.
			// Jak? Příklad: Type1 obsahuje property typu Type2, a Type2 obsahuje referenci na svého parenta = Type1.
			// Co by se stalo? Type1 se vytváří, najde property typu Type2, začal by ji mapovat ještě před uložením Type1,
			//   takže by se v tu chvíli začal mapovat opět Type1, atd...
			// Toto řešení (uložit nezmapovaný Type1 do Library a teprve potom jej zmapovat) tomu zabrání:
			info.Fill();
			return info;
		}
		/// <summary>
		/// Pokusí se najít daný Type ve své paměti předdefinovaných typů, anebo detekovat typ jako jeden ze známých, a vrátí jeho XmlPersistenceType.
		/// </summary>
		/// <param name="type"></param>
		/// <returns></returns>
		internal XmlPersistenceType GetPersistenceType(Type type, out TypeConvertor typeConvertor)
		{
			// Předdefinovaný Type?
			if (this._PresetTypes.TryGetValue(type, out typeConvertor))
				return typeConvertor.PersistenceType;

			if (type.ImplementInterface(typeof(IXmlSerializer))) return XmlPersistenceType.Self;
			if (type.IsEnum) return XmlPersistenceType.Enum;
			if (type.IsArray) return XmlPersistenceType.Array;
			if (type.IsInterface) return XmlPersistenceType.None;
			if (type.ImplementInterface(typeof(IList))) return XmlPersistenceType.IList;
			if (type.ImplementInterface(typeof(IDictionary))) return XmlPersistenceType.IDictionary;

			return XmlPersistenceType.Compound;
		}
		#endregion
		#region Simple types: příprava seznamu _PresetTypes, metoda AddTypeConvertor()
		/// <summary>
		/// Připraví seznam datových typů, které se ukládají Simple
		/// </summary>
		private void _SimpleTypePrepare()
		{
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Boolean), XmlPersistenceType.Simple, Convertor.BooleanToString, Convertor.StringToBoolean));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Byte), XmlPersistenceType.Simple, Convertor.ByteToString, Convertor.StringToByte));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.DateTime), XmlPersistenceType.Simple, Convertor.DateTimeToString, Convertor.StringToDateTime));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.DateTimeOffset), XmlPersistenceType.Simple, Convertor.DateTimeOffsetToString, Convertor.StringToDateTimeOffset));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Decimal), XmlPersistenceType.Simple, Convertor.DecimalToString, Convertor.StringToDecimal));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Double), XmlPersistenceType.Simple, Convertor.DoubleToString, Convertor.StringToDouble));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Guid), XmlPersistenceType.Simple, Convertor.GuidToString, Convertor.StringToGuid));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Char), XmlPersistenceType.Simple, Convertor.CharToString, Convertor.StringToChar));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Int16), XmlPersistenceType.Simple, Convertor.Int16ToString, Convertor.StringToInt16));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Int32), XmlPersistenceType.Simple, Convertor.Int32ToString, Convertor.StringToInt32));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Int64), XmlPersistenceType.Simple, Convertor.Int64ToString, Convertor.StringToInt64));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.IntPtr), XmlPersistenceType.Simple, Convertor.IntPtrToString, Convertor.StringToIntPtr));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.SByte), XmlPersistenceType.Simple, Convertor.SByteToString, Convertor.StringToSByte));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Single), XmlPersistenceType.Simple, Convertor.SingleToString, Convertor.StringToSingle));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.String), XmlPersistenceType.Simple, Convertor.StringToString, Convertor.StringToString));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.TimeSpan), XmlPersistenceType.Simple, Convertor.TimeSpanToString, Convertor.StringToTimeSpan));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.UInt16), XmlPersistenceType.Simple, Convertor.UInt16ToString, Convertor.StringToUInt16));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.UInt32), XmlPersistenceType.Simple, Convertor.UInt32ToString, Convertor.StringToUInt32));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.UInt64), XmlPersistenceType.Simple, Convertor.UInt64ToString, Convertor.StringToUInt64));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.UIntPtr), XmlPersistenceType.Simple, Convertor.UIntPtrToString, Convertor.StringToUIntPtr));

			this.AddTypeConvertor(new TypeConvertor(typeof(System.Data.SqlTypes.SqlBinary), XmlPersistenceType.Simple, Convertor.SqlBinaryToString, Convertor.StringToSqlBinary));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Data.SqlTypes.SqlBoolean), XmlPersistenceType.Simple, Convertor.SqlBooleanToString, Convertor.StringToSqlBoolean));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Data.SqlTypes.SqlByte), XmlPersistenceType.Simple, Convertor.SqlByteToString, Convertor.StringToSqlByte));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Data.SqlTypes.SqlDateTime), XmlPersistenceType.Simple, Convertor.SqlDateTimeToString, Convertor.StringToSqlDateTime));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Data.SqlTypes.SqlDecimal), XmlPersistenceType.Simple, Convertor.SqlDecimalToString, Convertor.StringToSqlDecimal));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Data.SqlTypes.SqlDouble), XmlPersistenceType.Simple, Convertor.SqlDoubleToString, Convertor.StringToSqlDouble));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Data.SqlTypes.SqlGuid), XmlPersistenceType.Simple, Convertor.SqlGuidToString, Convertor.StringToSqlGuid));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Data.SqlTypes.SqlInt16), XmlPersistenceType.Simple, Convertor.SqlInt16ToString, Convertor.StringToSqlInt16));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Data.SqlTypes.SqlInt32), XmlPersistenceType.Simple, Convertor.SqlInt32ToString, Convertor.StringToSqlInt32));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Data.SqlTypes.SqlInt64), XmlPersistenceType.Simple, Convertor.SqlInt64ToString, Convertor.StringToSqlInt64));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Data.SqlTypes.SqlMoney), XmlPersistenceType.Simple, Convertor.SqlMoneyToString, Convertor.StringToSqlMoney));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Data.SqlTypes.SqlSingle), XmlPersistenceType.Simple, Convertor.SqlSingleToString, Convertor.StringToSqlSingle));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Data.SqlTypes.SqlString), XmlPersistenceType.Simple, Convertor.SqlStringToString, Convertor.StringToSqlString));

            this.AddTypeConvertor(new TypeConvertor(typeof(System.Drawing.Color), XmlPersistenceType.Simple, Convertor.ColorToString, Convertor.StringToColor));
			// this.AddTypeConvertor(new TypeConvertor(typeof(System.Drawing.CharacterRange), XmlPersistenceType.Simple, null, null));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Drawing.Point), XmlPersistenceType.Simple, Convertor.PointToString, Convertor.StringToPoint));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Drawing.PointF), XmlPersistenceType.Simple, Convertor.PointFToString, Convertor.StringToPointF));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Drawing.Rectangle), XmlPersistenceType.Simple, Convertor.RectangleToString, Convertor.StringToRectangle));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Drawing.RectangleF), XmlPersistenceType.Simple, Convertor.RectangleFToString, Convertor.StringToRectangleF));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Drawing.Size), XmlPersistenceType.Simple, Convertor.SizeToString, Convertor.StringToSize));
			this.AddTypeConvertor(new TypeConvertor(typeof(System.Drawing.SizeF), XmlPersistenceType.Simple, Convertor.SizeFToString, Convertor.StringToSizeF));

			// Pokud zařadím další typy, musím k nim přidat jejich konvertory.
		}
		/// <summary>
		/// Několik vybraných typů, které se ukládají Simple
		/// </summary>
		private Dictionary<Type, TypeConvertor> _PresetTypes;
		/// <summary>
		/// Připraví seznam datových typů, které se ukládají Simple
		/// </summary>
		private void _PrepareTypes()
		{
			this._PresetTypes = new Dictionary<Type, TypeConvertor>();
			this._SimpleTypePrepare();
		}
		/// <summary>
		/// Přidá/modifikuje daný TypeConvertor
		/// </summary>
		/// <param name="typeConvertor"></param>
		internal void AddTypeConvertor(TypeConvertor typeConvertor)
		{
			if (!this._PresetTypes.ContainsKey(typeConvertor.DataType))
				this._PresetTypes.Add(typeConvertor.DataType, typeConvertor);
			else
				this._PresetTypes[typeConvertor.DataType] = typeConvertor;
		}
		private void _DisposeTypes()
		{
			this._PresetTypes.Clear();
			this._PresetTypes = null;
		}
		#endregion
		#region InstanceCreator

		/// <summary>
		/// Vygeneruje Compound objekt z aktuálního nodu XmlReaderu.
		/// Compound prvek obsahuje pouze jeden atribut : Net.Type="Namespace.TypeName",
		/// hodnota jednotlivých property je ve vnořeném elementu s názvem valueAttributeName.
		/// </summary>
		/// <param name="xmlReader"></param>
		/// <param name="type"></param>
		/// <param name="valueAttributeName"></param>
		/// <returns></returns>
		//private object _CompoundTypeCreate(XmlTextReader xmlReader, Type type, string valueAttributeName)
		//{
		//    object compound = _ObjectCreate(type);             // Vytvoří objekt
		//    if (compound == null) return null;

		//    // Soupis property, které se ukládaly (nebo by se ukládaly za aktuálního stavu objektu):
		//    TypeInfo typeInfo = this.GetInfo(type);
		//    int pc = typeInfo.PropertyList.Count;

		//    // Najdeme vnořený element "Value":
		//    xmlReader.MoveToContent();
		//    using (XmlElementReader eRead = XmlElementReader.ReadCurrentElement(xmlReader))
		//    {
		//        while (eRead.GoNext(true))
		//        {
		//            string el = eRead.CurrentNameLower;
		//            XmlNodeType nt = eRead.XmlReader.NodeType;
		//            if (eRead.XmlReader.NodeType == XmlNodeType.Element && eRead.CurrentNameLower == valueAttributeName.ToLower())
		//            {
		//                // 1. V atributech jsou hodnoty do Simple property:
		//                bool hasAtt = xmlReader.MoveToFirstAttribute();
		//                while (hasAtt)
		//                {
		//                    string attName = xmlReader.LocalName;
		//                    string attValue = xmlReader.ReadContentAsString();

		//                    hasAtt = xmlReader.MoveToNextAttribute();
		//                }

		//                // 2. Zpracujeme elementy, obsahují hodnoty do složených Property:


		//                // Další objekt nemohu hledat a vrátit, skončíme:
		//                eRead.GoEnd();
		//                break;
		//            }
		//        }
		//    }


		//    //if (xmlReader.HasAttributes)
		//    //{
		//    //    for (int i = 0; i < xmlReader.AttributeCount; i++)
		//    //    {
		//    //        xmlReader.MoveToAttribute(i);
		//    //        string attName = xmlReader.LocalName;

		//    //        string attTName = xmlReader.Name;
		//    //        string attValue = xmlReader.ReadContentAsString();
		//    //        xmlReader.GetAttribute(i);
		//    //    }
		//    //}

		//    return compound;


		//}
		/// <summary>
		/// Vytvoří a vrátí objekt daného typu
		/// </summary>
		/// <param name="type"></param>
		/// <returns></returns>
		private object _ObjectCreate(Type type)
		{
			object result = null;
			ConstructorInfo constructor = this.CheckConstructors(type);     // Ověří, zda Type má bezparametrický konstruktor. Vrátí jej.
			if (constructor != null)
				result = constructor.Invoke(null);
			else
				// Například struktury nemají bezparametrický konstruktor definovaný, proto vrací null. Přesto je lze standardně vytvořit:
				result = System.Activator.CreateInstance(type);
			return result;
		}
		/// <summary>
		/// Metoda ověří, zda typ má bezparametrický konstruktor.
		/// Pokud jej nemá, vyhodí chybu.
		/// Pokud jej má, vrátí jej.
		/// </summary>
		/// <param name="type"></param>
		/// <returns></returns>
		internal ConstructorInfo CheckConstructors(Type type)
		{
			if (type.IsClass)
			{
				ConstructorInfo[] typeConsts = type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);   // Najdu konstruktory daného Type
				ConstructorInfo[] typeConstNps = typeConsts.Where(c => c.GetParameters().Length == 0).ToArray();     // Najdu jen ty bezparametrické...
				if (typeConstNps.Length == 0)
					throw new InvalidOperationException("Type " + type.Namespace + "." + type.Name + " can not be persisted, must be a type with parameterless constructor!");
				return typeConstNps[0];
			}
			if (type.IsInterface)
			{
				throw new InvalidOperationException("Type " + type.Namespace + "." + type.Name + " is interface. Object can not be created.!");
			}
			if (type.IsValueType || type.IsEnum || type.IsPrimitive)
			{
				return null;
			}
			throw new InvalidOperationException("Type " + type.Namespace + "." + type.Name + " is unknown type. Object can not be created.!");
		}
		#endregion
        #region Static Type.Assembly podpora
        /// <summary>
        /// Určí text do atributu Type a Assembly pro daný Type.
        /// Pro některé typy není Assembly zapotřebí.
        /// Vrací takové informace, z nichž může reciproční metoda GetTypeFromSerial() vrátit Type.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        internal static void GetSerialForType(Type type, out string serialType, out string serialAssembly)
        {
            serialType = type.FullName;
            serialAssembly = null;

            // Někdy si chci uložit i assembly - to v případě, že v type.FullName není explicitně určena, a pro konkrétní datový typ ji budu potřebovat explicitně loadovat (v metodě GetTypeFromSerial()):

            // Pokud už v Type je obsažena explicitní informace o Assembly, pak končím:
            if (serialType.Contains("[") && serialType.Contains(" Version=")) return;

            // Pokud dokážu vytvořit Type jen na základě jeho názvu, pak nemusím ukládat explicitní jméno Assembly:
            Type test = Type.GetType(serialType);
            if (test != null) return;

            // Pokud dokážu vrátit assembly jen na základě názvu typu, pak nemusím ukládat její explicitní jméno:
            Assembly assm = _GetAssemblyFromTypeName(serialType);
            if (assm != null) return;

            // Protože z typu nedokážu určit assembly, musím si ji explicitně uložit teď, když mám typ:
            serialAssembly = type.Assembly.FullName;
        }
        /// <summary>
        /// Ze serializovaných údajů Type a Assembly vrátí Type daného objektu.
        /// </summary>
        /// <param name="serialType"></param>
        /// <param name="serialAssembly"></param>
        /// <returns></returns>
        internal static Type GetTypeFromSerial(string serialType, string serialAssembly)
        {
            if (String.IsNullOrEmpty(serialType)) return null;

            if (String.IsNullOrEmpty(serialAssembly))
            {
                Type result = Type.GetType(serialType);
                if (result != null) return result;

                Assembly assm = _GetAssemblyFromTypeName(serialType);
                if (assm == null) return null;

                return assm.GetType(serialType);
            }

            Assembly assLoad;
            Dictionary<string, Assembly> assDict = _LoadedAssemblyDict;
            if (!assDict.TryGetValue(serialAssembly, out assLoad))
            {
                AssemblyName assName = new AssemblyName(serialAssembly);
                assLoad = Assembly.Load(assName);
                assDict.Add(serialAssembly, assLoad);
            }
            return assLoad.GetType(serialType);
        }
        private static Dictionary<string, Assembly> _LoadedAssemblyDict
        {
            get
            {
                if (__LoadedAssemblyDict == null)
                    __LoadedAssemblyDict = new Dictionary<string, Assembly>();
                return __LoadedAssemblyDict;
            }
        }
        private static Dictionary<string, Assembly> __LoadedAssemblyDict;
        /// <summary>
        /// Zkusí určit Assembly pro daný název typu.
        /// </summary>
        /// <param name="serialType"></param>
        /// <returns></returns>
        private static Assembly _GetAssemblyFromTypeName(string serialType)
        {
            string nmsp = _GetNamespaceFromType(serialType);
            if (nmsp == "System") return typeof(System.Decimal).Assembly;
            if (nmsp == "System.Data") return typeof(System.Data.DataTable).Assembly;
            if (nmsp == "System.Drawing") return typeof(System.Drawing.Font).Assembly;
            if (nmsp == "System.Drawing.Design") return typeof(System.Drawing.Design.ToolboxItem).Assembly;
            if (nmsp == "System.Drawing.Drawing2D") return typeof(System.Drawing.Drawing2D.PathData).Assembly;
            if (nmsp == "System.Drawing.Imaging") return typeof(System.Drawing.Imaging.ColorMap).Assembly;
            if (nmsp == "System.Drawing.Printing") return typeof(System.Drawing.Printing.PageSettings).Assembly;
            if (nmsp == "System.Drawing.Text") return typeof(System.Drawing.Text.GenericFontFamilies).Assembly;
            if (nmsp == "System.Globalization") return typeof(System.Globalization.Calendar).Assembly;
            if (nmsp == "System.IO") return typeof(System.IO.DirectoryInfo).Assembly;

            return null;
        }
        /// <summary>
        /// Vrátí namespace typu, nebo jeho owner class pokud je to vnořená třída.
        /// </summary>
        /// <param name="serialType"></param>
        /// <returns></returns>
        private static string _GetNamespaceFromType(string serialType)
        {
            // Oddělit název typu před [ 
            //   "System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]" => "System.Collections.Generic.List`1"
            int ptr = serialType.IndexOf('[');
            string typeName = (ptr < 0 ? serialType : (ptr == 0 ? "" : serialType.Substring(0, ptr)));

            // Rozdělit název typu na položky: 
            //   "System.Drawing.Drawing2D.PathGradientBrush" => "System"; "Drawing"; "Drawing2D"; "PathGradientBrush"
            string[] typeItems = serialType.Split('.');
            string result = "";
            for (int t = 0; t < (typeItems.Length - 1); t++)
                result = result + (t == 0 ? "" : ".") + typeItems[t];
            return result;
        }
        #endregion
	}
	#endregion
	#region class TypeInfo : obecné informace o persistování jednoho datového typu. TypeInfo může obsahovat seznam PropInfo, anebo
	/// <summary>
	/// TypeInfo : obecné informace o persistování jednoho datového typu. TypeInfo může obsahovat seznam PropInfo, anebo 
	/// </summary>
	internal class TypeInfo
	{
		internal TypeInfo(TypeLibrary typeLibrary, Type dataType)
		{
			this.TypeLibrary = typeLibrary;
			this.DataType = dataType;
		}
		public override string ToString()
		{
			return this.DataType.Name + ": " + this.PersistenceType.ToString();
		}
		internal void Fill()
		{
			// Základní režim persistence (primitiv / datový objekt / soupis), plus najdu TypeConvertor:
			TypeConvertor typeConvert;
			this.PersistenceType = this.TypeLibrary.GetPersistenceType(this.DataType, out typeConvert);
			this.TypeConvert = typeConvert;

			// Detekce property do seznamu properties:
			switch (this.PersistenceType)
			{
                case XmlPersistenceType.Array:
                    this._DetectArrayItemType();
                    break;
				case XmlPersistenceType.IList:
				case XmlPersistenceType.IDictionary:
					break;
				case XmlPersistenceType.Compound:
					this._DetectProperties();
					break;
			}

			// Detekce generických typů proběhne vždy:
			this._DetectGenericArgs();
		}
		/// <summary>
		/// Reference na knihovnu typů.
		/// V ní jsem uložen já, v ní dohledávám informace pro svoje typy.
		/// </summary>
		internal TypeLibrary TypeLibrary { get; private set; }
		/// <summary>
		/// Můj Type.
		/// </summary>
		internal Type DataType { get; private set; }
		/// <summary>
		/// Typ persistence zdejšího typu (this.DataType).
		/// </summary>
		internal XmlPersistenceType PersistenceType { get; private set; }
		/// <summary>
		/// Type convertor, pokud pro tento typ existuje.
		/// </summary>
		internal TypeConvertor TypeConvert { get; private set; }
		/// <summary>
		/// Soupis property zdejšího typu.
		/// Je naplněn pouze při PersistenceType = XmlPersistenceType.InnerObject.
		/// </summary>
		internal List<PropInfo> PropertyList { get; private set; }
		#region Properties
		/// <summary>
		/// Detekuje PropertyInfo zdejšího typu a vytváří seznam this.PropertyList s prvky třídy PropInfo
		/// </summary>
		private void _DetectProperties()
		{
			this.PropertyList = new List<PropInfo>();
			PropertyInfo[] props = this.DataType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
			foreach (PropertyInfo prop in props)
			{
				PropInfo propData = new PropInfo(this, prop);
				if (!propData.Enabled) continue;

				this.PropertyList.Add(propData);
			}
			// Seznam setřídit:
			this.PropertyList.Sort(PropInfo.CompareByName);
		}
		#endregion
		#region Generika
        /// <summary>
        /// Počet generických argumentů
        /// </summary>
        internal int GenericCount
        {
            get
            {
                Type[] gps = this.DataType.GetGenericArguments();
                if (gps == null || gps.Length == 0) return 0;
                return gps.Length;
            }
        }
		/// <summary>
		/// Vrátí generický typ z dané pozice. Pokud daná pozice není obsazená, vrátí null.
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		internal Type GetGenericType(int index)
		{
			if (this.GenericList != null && index >= 0 && index < this.GenericList.Count)
				return this.GenericList[index];
			return null;
		}
		/// <summary>
        /// Detekuje typy generických parametrů do this.GenericParamList s prvky třídy TypeInfo.
        /// </summary>
        private void _DetectGenericArgs()
        {
            Type[] gps = this.DataType.GetGenericArguments();
            if (gps == null || gps.Length == 0) return;
            this.GenericList = new List<Type>();
            foreach (Type generic in gps)
                this.GenericList.Add(generic);
        }
		/// <summary>
		/// Soupis generických typů zdejšího typu.
		/// Je naplněn pouze při PersistenceType = XmlPersistenceType.InnerObject.
		/// </summary>
		internal List<Type> GenericList { get; private set; }
		#endregion
        #region Array
        /// <summary>
        /// Type prvků v tomto Array, pokud this popisuje standardní Array.
        /// Array má jeden typ, deklaruje se například: Int32[,] x = new Int32[5,12];
        /// Pak typ pole (this.DataType) = typeof(Int32[,]); a typ prvku this.ItemDataType = typeof(Int32);
        /// </summary>
        internal Type ItemDataType { get; private set; }
        /// <summary>
        /// Detekuje typ prvklů standardního Array, do this.ItemDataType
        /// </summary>
        private void _DetectArrayItemType()
        {
            Type arrayType = this.DataType;              // Typ pole, například = typeof(System.Int32[,])
            if (arrayType.HasElementType)
                this.ItemDataType = arrayType.GetElementType();      // Vrátí Type elementu pole, tedy typeof(System.Int32) !!! 
            else if (this.GenericCount > 0)
                this.ItemDataType = this.GetGenericType(0);
            else
                this.ItemDataType = null;
        }
        #endregion
        internal PropInfo FindPropertyByXmlName(string propName)
		{
			return this.PropertyList.FirstOrDefault(p => String.Equals(p.XmlName, propName));
		}
    }
	#endregion
	#region class PropInfo : data o jedné property (jméno property, její typ, její TypeInfo
	/// <summary>
	/// PropInfo : data o jedné property
	/// </summary>
	internal class PropInfo
	{
		internal PropInfo(TypeInfo typeInfo, PropertyInfo property)
		{
			this.TypeInfo = typeInfo;
			this.Property = property;
            this._PropertyTypeInfo = null;

            // Property lze ukládat, pokud má set metodu:
			this.Enabled = (property.GetSetMethod(true) != null);
			this.XmlName = null;

            // Zjistím, zda nejde o property "Djs.Tools.XmlPersistor.IXmlPersistNotify.XmlPersistState" (takovou nebudu persistovat zcela automaticky):
            object[] atts = null;
            // Anebo, zda property má atribut PersistingEnabledAttribute s hodnotou PersistEnable = false, pak by se neukládala:
            if (!this.Enabled || property.Name == "Djs.Tools.XmlPersistor.IXmlPersistNotify.XmlPersistState" || !IsPropertyPersistable(property, out atts))
            {
                this.Enabled = false;
                return;
            }
            if (atts == null)
                atts = property.GetCustomAttributes(typeof(PersistAttribute), true);

			// XmlName (Custom / default):
			object attName = atts.FirstOrDefault(at => at is PropertyNameAttribute);
			if (attName != null)
				this.XmlName = (attName as PropertyNameAttribute).PropertyName;
			if (String.IsNullOrEmpty(this.XmlName))
				this.XmlName = this.Name;
            this.XmlName = XmlPersist.CreateXmlName(this.XmlName);

			// XmlItemName
			object attItem = atts.FirstOrDefault(at => at is CollectionItemNameAttribute);
			if (attItem != null)
				this.XmlItemName = (attItem as CollectionItemNameAttribute).ItemName;
			if (String.IsNullOrEmpty(this.XmlItemName))
				this.XmlItemName = "Item";
            this.XmlItemName = XmlPersist.CreateXmlName(this.XmlItemName);
		}
        /// <summary>
        /// Určí, zda daná property se má persistovat (ukládat + načítat z XML)
        /// </summary>
        /// <param name="property"></param>
        /// <returns></returns>
        internal static bool IsPropertyPersistable(PropertyInfo property)
        {
            object[] atts;
            return IsPropertyPersistable(property, out atts);
        }
        /// <summary>
        /// Určí, zda daná property se má persistovat (ukládat + načítat z XML)
        /// </summary>
        /// <param name="property"></param>
        /// <returns></returns>
        internal static bool IsPropertyPersistable(PropertyInfo property, out object[] atts)
        {
            // Custom atributy:
            atts = property.GetCustomAttributes(typeof(PersistAttribute), true);
            object attEnabled = atts.FirstOrDefault(at => at is PersistingEnabledAttribute);

            // Pokud existuje atribut PersistingEnabledAttribute, pak vrátím jeho hodnotu PersistEnable:
            if (attEnabled != null)
                return (attEnabled as PersistingEnabledAttribute).PersistEnable;

            // Pokud neexistuje, vrátím true = implicitně persistuji vše:
            return true;
        }
        /// <summary>
        /// Určí, zda daná property se má persistovat (ukládat + načítat z XML)
        /// </summary>
        /// <param name="property"></param>
        /// <returns></returns>
        internal static bool IsPropertyCloneable(PropertyInfo property)
        {
            // Custom atributy:
            object[] atts = property.GetCustomAttributes(typeof(PersistAttribute), true);
            object attEnabled = atts.FirstOrDefault(at => at is PersistingEnabledAttribute);

            // Pokud existuje atribut PersistingEnabledAttribute, pak vrátím jeho hodnotu CloneEnable:
            if (attEnabled != null)
                return (attEnabled as PersistingEnabledAttribute).CloneEnable;

            // Pokud neexistuje, vrátím true = implicitně persistuji vše:
            return true;

        }
        public override string ToString()
		{
			string text = this.Property.Name + ": ";
			if (this.Enabled)
				text += "XML name=" + this.XmlName + "; Type=" + this.PropertyType.Name;
			else
				text += "disabled.";
			return text;
		}
		internal static int CompareByName(PropInfo a, PropInfo b)
		{
			return String.Compare(a.Name, b.Name);
		}
		/// <summary>
		/// Vztah na typ, jehož je tato property členem
		/// </summary>
		internal TypeInfo TypeInfo { get; private set; }
		/// <summary>
		/// Reference na knihovnu typů.
		/// V ní je uložen můj TypeInfo, a přes něj se do knihovny dostanu i já.
		/// </summary>
		internal TypeLibrary TypeLibrary { get { return this.TypeInfo.TypeLibrary; } }
		/// <summary>
		/// Info o této property
		/// </summary>
		internal PropertyInfo Property { get; private set; }
		/// <summary>
		/// Název této property = exaktně z this.Property.Name
		/// </summary>
		internal string Name { get { return this.Property.Name; } }
		/// <summary>
		/// .NET Type této property = this.Property.PropertyType.
		/// Jde o typ, jak je property deklarována, nikoli Type, který je v ní aktuálně uložen (to se zjistí až podle objektu s daty).
		/// Může to být potomek zdejšího typu, anebo implementace interface...
		/// </summary>
		internal Type PropertyType { get { return this.Property.PropertyType; } }
		/// <summary>
		/// true, pokud se tato property má persistovat
		/// </summary>
		internal bool Enabled { get; private set; }
		/// <summary>
		/// Rozšířené vlastnosti typu této property - data, načtená z TypeLibrary.
		/// Data se načítají až on demand, z TypeLibrary.
		/// </summary>
		internal TypeInfo PropertyTypeInfo
		{
			get
			{
				if (this._PropertyTypeInfo == null)
					this._PropertyTypeInfo = this.TypeLibrary.GetInfo(this.PropertyType);
				return this._PropertyTypeInfo;
			}
		}
		private TypeInfo _PropertyTypeInfo;
		/// <summary>
		/// Jméno pro persistenci hodnoty této property
		/// </summary>
		internal string XmlName { get; private set; }
		/// <summary>
		/// Jméno pro persistenci jednotlivých položek kolekce (Item)
		/// </summary>
		internal string XmlItemName { get; private set; }
		#region Vytváření jména elementu - asi zbytečné
		/// <summary>
		/// Ze jména property vytvoří jméno XML elementu/atributu
		/// </summary>
		/// <param name="objectName"></param>
		/// <returns></returns>
		private string _XmlCreateName(string objectName)
		{
			string xmlName = "";
			string upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
			string lower = "abcdefghijklmnopqrstuvwxyz";
			string number = "0123456789";
			string diacriticUpp = "ÁĆČĎÉĚËÍĹĽŇÓŔŘŠŤÚŮÝŽ";
			string noDiacritUpp = "ACCDEEEILLNORRSTUUYZ";
			string diacriticLow = "áćčďéěëíĺľňóŕřśšťúůýž";
			string noDiacritLow = "accdeeeillnorrsstuuyz";
			string diacritic = diacriticUpp + diacriticLow;
			string noDiacrit = noDiacritUpp + noDiacritLow;
			_XmlCreateSequence state = _XmlCreateSequence.Begin;
			for (int i = 0; i < objectName.Length; i++)
			{
				string c = objectName.Substring(i, 1);

				// Zrušit diakritiku
				int diacr = diacritic.IndexOf(c);
				if (diacr > 0)
					c = noDiacrit.Substring(diacr, 0);

				// Pokud mám ve vstupujícím textu podtržítko:
				if (c == "_")
				{
					if (state != _XmlCreateSequence.AfterUnders)
					{   // Pokud nejsem po explicitním podtržítku, tak tam první dát můžu (ale další už ne):
						xmlName += c;
						state = _XmlCreateSequence.AfterUnders;
					}
				}
				// Předsadit _ před číslice:
				else if (number.Contains(c))
				{   // Máme číslici:
					if (state == _XmlCreateSequence.AfterLower || state == _XmlCreateSequence.AfterUpper)
						// po jakémkoli písmenu => předsadím _, po _ a po číslici nepředsadím:
						xmlName += "_";
					xmlName += c.ToLower();
					state = _XmlCreateSequence.AfterNumber;
				}
				// Řešit konverzi CamelCase => camel_case
				else if (upper.Contains(c))
				{   // Máme velké písmeno:
					if (state == _XmlCreateSequence.AfterLower || state == _XmlCreateSequence.AfterNumber)
						// po malém písmenu nebo po číslici => předsadím _, po velkém písmenu a po _ ne:
						xmlName += "_";
					xmlName += c.ToLower();
					state = _XmlCreateSequence.AfterUpper;
				}
				else
				{
					xmlName += c.ToLower();
					state = _XmlCreateSequence.AfterLower;
				}
			}
			return xmlName;
		}
		private enum _XmlCreateSequence { Begin, AfterMultiUpper, AfterUpper, AfterLower, AfterNumber, AfterUnders }
		#endregion
	}
	#endregion
    #region Extensions
    internal static class Extensions
    {
        /// <summary>
        /// Vrátí true, pokud this type implementuje daný interface.
        /// Je obdobou testu "if (data is implementInterface)", ale pro případ, kdy nemáme objekt ale jeho Type.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="implementInterface"></param>
        /// <returns></returns>
        internal static bool ImplementInterface(this Type type, Type implementInterface)
        {
            return type.GetInterfaces().Any(i => Type.Equals(i, implementInterface));
        }
    }
    #endregion
}
